#ifndef __TDP2_MEMZONE_HPP__
#define __TDP2_MEMZONE_HPP__

#include "../../rdx/src/rdx/rdx_basictypes.hpp"

namespace TDP
{
	using namespace RDX;

	namespace MemoryManagement
	{
		inline LargeUInt PadForAddress(const void *ptr, LargeUInt align)
		{
			LargeUInt misalignment = (static_cast<const UInt8 *>(ptr) - reinterpret_cast<const UInt8 *>(NULL)) % align;
			if(misalignment == 0)
				return 0;
			return align - misalignment;
		}

		struct MemoryAllocator
		{
			// Attempts to resize a buffer to a different size
			// If successful, returns "true"
			// If unsuccessful, returns "false" and returns the amount of data that could be copied from this location
			// to a larger buffer (which may be larger than its actual contents, but not larger than a size this could
			// have been resized to)
			virtual bool Resize(void *ptr, LargeUInt size, LargeUInt &outUsedSpace) = 0;
			virtual void *Alloc(LargeUInt sz, LargeUInt align) = 0;
			virtual void Free(void *ptr) = 0;

			static void *realloc(void *opaque, void *ptr, LargeUInt sz, LargeUInt alignment);
		};

		class FreeListAllocator : public MemoryAllocator
		{
		private:
			struct FreeListLink
			{
				FreeListLink *next;
			};

			FreeListLink *_firstFree;
			
			FreeListLink *_links;

			LargeUInt _unitSize;
			LargeUInt _unitAlign;
			UInt8 *_units;

		public:
			inline FreeListAllocator() { }
			FreeListAllocator(void *space, LargeUInt spaceAvailable, LargeUInt unitSize, LargeUInt unitAlign);

			bool Resize(void *ptr, LargeUInt size, LargeUInt &outUsedSpace);
			void *Alloc(LargeUInt sz, LargeUInt align);
			void Free(void *ptr);
		};

		class ZoneAllocator : public MemoryAllocator
		{
		private:

			struct ZoneBlock
			{
				LargeUInt usedSpace;			// Used space, including the header
				LargeUInt blockSize;			// Total size of the block, including unused space

				LargeUInt prevBlockOffset;	// Offset to the previous block header
			};

			UInt8 *_zoneBytes;
			ZoneBlock *_lastBlock;
			const void *_endOfBlock;
			LargeUInt _blockAlignment;
			LargeUInt _totalBytes;

			void *AllocInBlock(ZoneBlock *block, LargeUInt sz, LargeUInt align);
			static ZoneBlock *PtrToBlock(void *ptr, LargeUInt &outHeaderSize);
			inline static ZoneBlock *PtrToBlock(void *ptr)
			{
				LargeUInt discard;
				return PtrToBlock(ptr, discard);
			}

		public:
			inline ZoneAllocator() { }
			ZoneAllocator(void *memBlock, LargeUInt memBlockSize, LargeUInt blockAlignment);

			// Attempts to resize an existing block to its new size.  Returns "true" if the resize was successful.
			bool Resize(void *ptr, LargeUInt size, LargeUInt &outUsedSpace);
			void Free(void *ptr);
			void *Alloc(LargeUInt sz, LargeUInt align);
		};

		class MultiAllocator
		{
		public:
			struct Region
			{
				void *regionStart;
				LargeUInt regionSize;
				MemoryAllocator *opaque;

				inline bool ContainsPointer(const void *ptr) const
				{
					return ptr >= regionStart && ptr < reinterpret_cast<const UInt8 *>(regionStart) + regionSize;
				}
			};

		private:
			const Region *_regions;
			LargeUInt _numRegions;

		public:
			MultiAllocator(const Region *regions, LargeUInt numRegions);
			bool Resize(void *ptr, LargeUInt size, LargeUInt &outUsedSpace);
			void Free(void *ptr);
			void *Alloc(LargeUInt sz, LargeUInt align);
		};
	}
}

#endif
